package common

import (
	"sort"

	"github.com/golang/protobuf/proto"
	"hundsun.com/hsl/hschain/blockchain/impl/pubsub"
	"hundsun.com/hsl/hschain/common/types"
	"hundsun.com/hsl/hschain/common/util/pb"
	"hundsun.com/hsl/hschain/contract/native/manage/common"
	commonproto "hundsun.com/hsl/hschain/protos/common"
	"hundsun.com/hsl/hschain/protos/config"
	dbm "hundsun.com/hsl/hschain/store/db"
	"hundsun.com/hsl/hschain/store/extend/ledger"
	"hundsun.com/hsl/hschain/store/mq"
)

// InitParam ...
func (bc *BaseClient) InitParam() {
	if bc.curParam == nil {
		bc.curParam = &config.SysParam{}
	}
	genesisTransactionPayload := pb.GetGenesisTransactionPayload()
	if genesisTransactionPayload != nil {
		bc.curParam.Difficulty = genesisTransactionPayload.Difficulty
		bc.curParam.TimeLimit = genesisTransactionPayload.TimeLimit
		bc.curParam.Crypto = genesisTransactionPayload.Crypto
		bc.curParam.CheckBlock = genesisTransactionPayload.CheckBlock
		bc.curParam.Manager = genesisTransactionPayload.Manager
		bc.curParam.BlockLimit = genesisTransactionPayload.BlockLimit
		bc.curParam.GasLimit = genesisTransactionPayload.GasLimit
		bc.curParam.Reward = genesisTransactionPayload.Reward
		bc.curParam.FeeLimit = genesisTransactionPayload.FeeLimit

		consensusType := genesisTransactionPayload.Consensus.GetType()
		if consensusType == config.SoloConsensusType {
			var soloParam = &config.ConsensusParam_SoloPayload{SoloPayload: &config.SoloConsensusConfigPayload{}}
			err := proto.Unmarshal(genesisTransactionPayload.Consensus.GetPayload(), soloParam.SoloPayload)
			if err != nil {
				panic(err.Error())
			}
			var consensuParam = &config.ConsensusParam{}
			consensuParam.Type = config.SoloConsensusType
			consensuParam.ConsensusPayload = soloParam
			bc.curParam.Consensus = consensuParam
		} else if consensusType == config.DposConsensusType {
			var dposParam = &config.ConsensusParam_DposPayload{DposPayload: &config.DposConsensusConfigPayload{}}
			err := proto.Unmarshal(genesisTransactionPayload.Consensus.GetPayload(), dposParam.DposPayload)
			if err != nil {
				panic(err.Error())
			}
			var consensuParam = &config.ConsensusParam{}
			consensuParam.Type = config.DposConsensusType
			consensuParam.ConsensusPayload = dposParam
			bc.curParam.Consensus = consensuParam
		} else {
			panic("unknown consensus type")
		}
	}

	err := bc.LoadCurParam()
	if err != nil {
		panic(err)
	}

	err = bc.recoverStepParamList()
	if err != nil {
		panic(err)
	}

	err = bc.recoverStepConList()
	if err != nil {
		panic(err)
	}

	bc.log.Infof("init consensus system param successfully.")
}

func (bc *BaseClient) getMaxExecutedBlockNum() (uint64, error) {
	service, err := ledger.NewLedger(bc.GetLedgerDB())
	if err != nil {
		return 0, err
	}

	maxExecBlkNum, err := service.GetMaxExecutedBlockNumber()
	if err != nil && err != types.ErrNotFound {
		panic(err)
	}
	return maxExecBlkNum, nil
}

func (bc *BaseClient) recoverStepParamList() error {
	if bc.GetLedgerDB() != nil {
		list := bc.GetLedgerDB().NewList()
		count := list.PrefixCount(types.GeneSysParamUpdateKeyPrefix())
		if count > 0 {
			_, sysParamVals, err := list.ListPrefix(types.GeneSysParamUpdateKeyPrefix(), int32(count), dbm.OptionReverse|dbm.OptionWithValue)
			if len(sysParamVals) > 0 {
				for _, sysParamVal := range sysParamVals {
					var sysParam = &config.StepSysParam{}
					err = proto.Unmarshal(sysParamVal, sysParam)
					if err != nil {
						return err
					}
					bc.addStepParam(sysParam)
				}
			}
		}
	}
	return nil
}

func (bc *BaseClient) recoverStepConList() error {
	if bc.GetLedgerDB() != nil {
		list := bc.GetLedgerDB().NewList()
		count := list.PrefixCount(types.GeneConsensusUpdateKeyPrefix())
		if count > 0 {
			_, conParamVals, err := list.ListPrefix(types.GeneConsensusUpdateKeyPrefix(), int32(count), dbm.OptionReverse|dbm.OptionWithValue)
			if len(conParamVals) > 0 {
				for _, conParamVal := range conParamVals {
					var stepConParam = &config.StepConParam{}
					err = proto.Unmarshal(conParamVal, stepConParam)
					if err != nil {
						return err
					}
					bc.addConsensusList(stepConParam, nil)
				}
			}
		}
	}
	return nil
}

// GetCurParam ...
func (bc *BaseClient) GetCurParam() *config.SysParam {
	return bc.curParam
}

// SetCurParam ...
func (bc *BaseClient) SetCurParam(curParam *config.SysParam) {
	if curParam != nil {
		if curParam.TimeLimit != nil {
			bc.curParam.TimeLimit = curParam.TimeLimit
		}
		if curParam.Difficulty != 0 {
			bc.curParam.Difficulty = curParam.Difficulty
		}
		if curParam.Reward != 0 {
			bc.curParam.Reward = curParam.Reward
		}
		if curParam.GasLimit != 0 {
			bc.curParam.GasLimit = curParam.GasLimit
		}
		if curParam.Crypto != nil {
			bc.curParam.Crypto = curParam.Crypto
		}
		if curParam.CheckBlock != nil {
			bc.curParam.CheckBlock = curParam.CheckBlock
		}
		if curParam.BlockLimit != nil {
			bc.curParam.BlockLimit = curParam.BlockLimit
		}
		if curParam.FeeLimit != nil {
			bc.curParam.FeeLimit = curParam.FeeLimit
		}
		if curParam.Manager != nil {
			bc.curParam.Manager = curParam.Manager
		}
		if curParam.Consensus != nil {
			bc.curParam.Consensus = curParam.Consensus
		}
	}
}

func (bc *BaseClient) subSysParamUpdateEvent() error {
	//封装订阅消息
	router := &commonproto.Router{
		MainTopic: types.ConsensusModule,
		SubTopic:  mq.ModeWorker.String(),
		EventType: pubsub.SubSysParam,
	}

	consensParamSub := &commonproto.SystemParam{
		StartNum: 0,
		Contract: types.GenSysContractName(common.NativeManage),
		Event: []string{
			types.GenSysContractName(types.ContractEventSysParamUpdate),
			types.GenSysContractName(types.ContractEventConsensusUpdate),
		},
	}

	sysParam := &commonproto.ReqSubscriber_System{
		System: &commonproto.SystemParams{Params: []*commonproto.SystemParam{consensParamSub}},
	}

	data := &commonproto.ReqSubscriber{
		MsgRouter: router,
		Type:      pubsub.SubSysParam,
		Content:   sysParam,
	}

	msg := bc.workerClient.NewMessage(types.BlockChainModule, mq.ModeWorker.String(), types.EventSubscriber, data)
	err := bc.workerClient.Send(msg, false)
	if err != nil {
		return err
	}
	bc.log.Infof("subscribe message successfully, event: %v", consensParamSub.Event)
	return nil
}

func (bc *BaseClient) subNewBlock() error {
	//封装订阅消息
	router := &commonproto.Router{
		MainTopic: types.ConsensusModule,
		SubTopic:  mq.ModeTopic.String(),
		EventType: pubsub.SubBlockHeader,
	}

	data := &commonproto.ReqSubscriber{
		MsgRouter: router,
		Type:      pubsub.SubBlockHeader,
	}

	msg := bc.workerClient.NewMessage(types.BlockChainModule, mq.ModeWorker.String(), types.EventSubscriber, data)
	err := bc.workerClient.Send(msg, false)
	if err != nil {
		return err
	}
	bc.log.Info("subscribe new block header successfully")
	return nil
}

// ProcSubSysParam ...
func (bc *BaseClient) ProcSubSysParam(msg *mq.Message) {
	resp := msg.Data.(*commonproto.RspSubscriber)
	reply := &commonproto.Reply{IsOk: true}
	blkNum := uint64(resp.GetSystem().GetBlockNum())
	sysDatas := resp.GetSystem().GetDatas()
	for _, data := range sysDatas {
		bc.log.Debugf("process system data: %s", data.Event)
		switch data.Event {
		case string(types.GeneSysParamUpdateKeyPrefix()):
			var stepSysParam = &config.StepSysParam{}
			err := proto.Unmarshal(data.Data, stepSysParam)
			if err != nil {
				reply.IsOk = false
				bc.log.Errorf("unmarshal system param failed, err: %s", err.Error())
				break
			}

			if stepSysParam.EffectiveHeight == 0 {
				stepSysParam.EffectiveHeight = blkNum
			}
			bc.addStepParam(stepSysParam)
			bc.log.Infof("update system param successfully, %v", stepSysParam)
		case string(types.GeneConsensusUpdateKeyPrefix()):
			var stepConParam = &config.StepConParam{}
			err := proto.Unmarshal(data.Data, stepConParam)
			if err != nil {
				reply.IsOk = false
				bc.log.Errorf("unmarshal consensus update param failed, err: %s", err.Error())
				break
			}

			if stepConParam.EffectiveHeight == 0 {
				stepConParam.EffectiveHeight = blkNum
			} else if stepConParam.EffectiveHeight < blkNum {
				bc.log.Warnf("consensus update height %d already passed, ignore this step", stepConParam.EffectiveHeight)
				continue
			}

			if bc.availableAddConList(stepConParam.EffectiveHeight) {
				bc.addConsensusList(stepConParam, nil)
				bc.log.Infof("current length of consensus list is %d", len(stepConList))
				bc.log.Infof("update consensus param successfully, conType %s and height %d", stepConParam.GetConParam().GetType(), stepConParam.EffectiveHeight)
			}
		default:
			bc.log.Errorf("unknown event type: %s", data.Event)
		}
	}

	replyMsg := bc.workerClient.NewMessage("", "", msg.Ty, reply)
	msg.Reply(replyMsg)

	bc.log.Infof("receive subscribe system param msg, blockNumber:%d", blkNum)
}

// GetCurrentMiners ...
func (bc *BaseClient) GetCurrentMiners() []*commonproto.Miner {
	var genesisMiner []*commonproto.Miner
	if bc.GetCurParam() != nil {
		switch bc.GetCurParam().GetConsensus().GetType() {
		case config.SoloConsensusType:
			payload := bc.GetCurParam().GetConsensus().GetSoloPayload()
			genesisMiner = append(genesisMiner, &commonproto.Miner{Address: payload.GetAddress()})
		case config.DposConsensusType:
			payload := bc.GetCurParam().GetConsensus().GetDposPayload()
			genesisMiner = append(genesisMiner, payload.GetDefaultMiners()...)
		}
	}

	return genesisMiner
}

// AddStepParam add step param
func (bc *BaseClient) addStepParam(stepParam *config.StepSysParam) {
	bc.stepParamMutex.Lock()
	defer bc.stepParamMutex.Unlock()

	stepParamList = append(stepParamList, stepParam)
	sort.Slice(stepParamList, func(i, j int) bool {
		return stepParamList[i].EffectiveHeight < stepParamList[j].EffectiveHeight
	})
}

func (bc *BaseClient) getParamByHeight(height uint64) *config.StepSysParam {
	bc.stepParamMutex.RLock()
	defer bc.stepParamMutex.RUnlock()

	for _, stepParam := range stepParamList {
		if stepParam.EffectiveHeight == height {
			return stepParam
		}
	}
	return nil
}

// StepParamListCheck step system param list check
func (bc *BaseClient) StepParamListCheck(height uint64) {
	stepParam := bc.getParamByHeight(height)
	if stepParam != nil {
		if stepParam.GetCurParam() != nil && stepParam.GetCurParam().GetConsensus() != nil &&
			bc.GetCurParam().GetConsensus().GetType() != stepParam.GetCurParam().GetConsensus().GetType() &&
			bc.GetCurParam().GetConsensus().GetType() == config.SoloConsensusType {
			var stepConParam = &config.StepConParam{
				ConParam:        stepParam.GetCurParam().GetConsensus(),
				EffectiveHeight: stepParam.GetEffectiveHeight(),
			}

			if bc.availableAddConList(stepConParam.GetEffectiveHeight()) {
				bc.addConsensusList(stepConParam, nil)
			}
		}
		bc.SetCurParam(stepParam.SysParam)
		bc.SaveCurParam()
		bc.log.Infof("current param update, current block height %d", height)
	}
}

// SaveCurParam save current param
func (bc *BaseClient) SaveCurParam() {
	conParamInfo, err := proto.Marshal(bc.GetCurParam())
	if err != nil {
		panic(err)
	}
	conParamKey := geneSysParamKey()
	err = bc.GetLedgerDB().Set(conParamKey, conParamInfo)
	if err != nil {
		panic(err)
	}
}

// LoadCurParam load current param
func (bc *BaseClient) LoadCurParam() error {
	if bc.GetLedgerDB() != nil {
		sysParamKey := geneSysParamKey()
		sysParamBytes, err := bc.GetLedgerDB().Get(sysParamKey)
		if err != nil && err != types.ErrNotFound {
			return err
		}
		if len(sysParamBytes) > 0 {
			var sysParam = &config.SysParam{}
			err = proto.Unmarshal(sysParamBytes, sysParam)
			if err != nil {
				return err
			}
			bc.SetCurParam(sysParam)
		}
	}
	return nil
}

func geneSysParamKey() []byte {
	// GenSysParamKey 共识配置Key
	return []byte(dbm.NamespaceSystemParam + "con")
}
